#pragma once
#include "ZhicheEntity.h"
#include "MarkerMgr.h"

class ModelMgr : protected IEntityMgr {
public:
	static ModelMgr* get() {
		static auto instance = new ModelMgr;
		return instance;
	}

	/**@brief called per frame, at rendering thread */
	void updateEntities() override {
		bool hasNewData = Cache::useNewlyRcvdModels();

		handleSQStatus();

		const auto& allArgMap = Cache::modelArgMap();
		for (const auto& kv : allArgMap) {
			auto& arg = kv.value;
			Cache::increaseUpdateFrameCnt(arg.ID);
			if (!arg.identified) {/**@deprecated currently not used*/
				removeModelAndGlobalCache(arg.ID);
				continue;
			}
			if (hasNewData)
				updateModel(arg);
		}
		updateZhiche();

		clearExpiredModels();
	}

	void clear() override {
		auto modelArgMap = Cache::modelArgMap();
		for (const auto& kv: modelArgMap) {
			auto& arg = kv.value;
			removeModel(arg.ID);
			Cache::removeModel(arg.ID);
		}
		Scene::remove(m_zhiche);
		m_zhiche = nullptr;

		removeSQZhicheCircle();

		if (Proj::isSSQK())
			MarkerMgr::get()->clear();
	}

	/** do not turn to single-instance mode, may lead to diff-thread-create and unnecessary init*/
	ZhicheEntity* zhiche() { return m_zhiche; }

	void handleZhicheDoor(HDir side, Switch status) const {
		if (!m_zhiche) return;
		m_zhiche->handleDoor(side, status);
	}
	void resetZhicheDoor() const {
		handleZhicheDoor(HDir::LEFT, Switch::OFF);
		handleZhicheDoor(HDir::RIGHT, Switch::OFF);
	}

private:
	ModelMgr() = default;

	/** @warning condition exists: ID keep the same, but ModelType change! */
	void updateModel(const ModelArg& arg) {
		auto ID = arg.ID;
		ModelEntity* model;
		if (!m_modelMap.contains(ID))
			model = addModel(arg);
		else {
			model = m_modelMap[ID];
			/**@note same target object, model type recognition changed*/
			if (model->modelType() != arg.type) {
				removeModel(ID);
				model = addModel(arg);
			}
		}
		model->updateState();

		MarkerMgr::get()->updateModelAccFD(ID);

		if (Proj::isSSQK())
			MarkerMgr::get()->updateModelAccCircle(ID);
	}

	void updateZhiche() {
		updateSQZhicheCircle();// display breathing-circle rapidly(meanwhile,zhiche is a cached PNG)

		if (!m_zhiche) {
			m_zhiche = ZhicheEntity::New();
			if (!m_zhiche)
				return;
			Scene::add(m_zhiche);
		}
		m_zhiche->updateState();
	}

	ModelEntity* addModel(const ModelArg& arg) {
		auto model = new ModelEntity(arg.type, arg.ID);
		m_modelMap.insert(arg.ID, model);
		Scene::add(model);
		return model;
	}

	void removeModel(int ID) {
		Scene::remove(m_modelMap.take(ID));
		MarkerMgr::get()->removeModelAccFD(ID);
		MarkerMgr::get()->removeModelAccCircle(ID);// SSQK
	}

	/**
	 *@important be careful: do not remove the global-cached model-datas too early!
	 * because [in a same frame], many other place have to rely on the global-cached model-datas!
	 */
	void removeModelAndGlobalCache(int ID) {
		removeModel(ID);
		Cache::removeModel(ID);
	}

	void clearExpiredModels() {
		const auto ExpireUpdateCnt = Config::Mechanism_ExpireUpdateCnt;
		const auto ExpireFrameCnt = Config::Mechanism_ExpireFrameCnt;
		const auto modelArgMap = Cache::modelArgMap();
		for (const auto& kv: modelArgMap) {
			auto& arg = kv.value;
			if (arg.updateCnt >= ExpireUpdateCnt || arg.updateFrameCnt >= ExpireFrameCnt)
				removeModelAndGlobalCache(arg.ID);
		}
	}

	CMap<int, ModelEntity*> m_modelMap;
	ZhicheEntity* m_zhiche = nullptr;

	/******************************* SQ-Android *******************************/
	/******************************* SQ-Android *******************************/
	void removeSQZhicheCircle() {
		if (!Proj::isSQ())
			return;
		MarkerMgr::get()->remove(MarkerType::ZhicheCircle);
	}

	void updateSQZhicheCircle() {
		if (!Proj::isSQ())
			return;
		if (!Cache::isReceivingData()) {
			removeSQZhicheCircle();
			return;
		}
		MarkerMgr::get()->update(MarkerType::ZhicheCircle);
	}

	void handleSQStatus() {
		if (!Proj::isSQ())
			return;
		const auto& Status = Cache::sqStatus();
			/**@note FCW has highest priority: 1 */
			bool isFCWValid = Status.fcw != sq::FCW::INVALID && Status.fcw != sq::FCW::Off;
			if (isFCWValid) {
				int closestFrontID = Cache::getFrontModelIdClosestToOrigin();
				if (closestFrontID != -1) {
					auto& model = Cache::modelArgRef(closestFrontID);
					if (Status.fcw == sq::FCW::Yellow) model.color = "yellow";
					else if (Status.fcw == sq::FCW::Red) model.color = "red";
				}
			} else {
				/**@note ACAS has priority: 2 */
				bool isACASValid = Status.acas != sq::ACAS::INVALID && Status.acas != sq::ACAS::Off;
				if (isACASValid) {
					HDir towards = (Status.acas == sq::ACAS::Left ? HDir::RIGHT : HDir::LEFT);
					auto IDs = Cache::getRangeModelIDs(sq::TARGET_RANGE::ACAS_H_RADIUS, sq::TARGET_RANGE::ACAS_V_RADIUS, towards);
					for (auto id : IDs) {
						auto& model = Cache::modelArgRef(id);
						model.color = "yellow";
					}
				} else {
					/**@note ALC has priority: 3 */
					bool isALCValid = Status.noa != sq::NOA::On && Status.alc != sq::ALC::INVALID && Status.alc != sq::ALC::Off;
					if (isALCValid) {
						HDir towards = (Status.alc == sq::ALC::Left ? HDir::LEFT : HDir::RIGHT);
						auto IDs = Cache::getRangeModelIDs(sq::TARGET_RANGE::ALC_H_RADIUS, sq::TARGET_RANGE::ALC_V_RADIUS, towards);
						for (auto id : IDs) {
							auto& model = Cache::modelArgRef(id);
							model.color = "yellow";
						}
					} else {
						/**@note ACC has priority: 4 */
						bool isACCValid = Status.acc != sq::ACC::INVALID && Status.acc != sq::ACC::Off;
						if (isACCValid) {
							int closestFrontID = Cache::getFrontModelIdClosestToOrigin();
							if (closestFrontID != -1) {
								auto& model = Cache::modelArgRef(closestFrontID);
								if (isACCValid && Status.acc == sq::ACC::Follow)
									model.color = "blue";
							}
						}
					}
				}
			}
	}
};